﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.IO;

namespace WebServer
{
    public class HttpResponseCore
    {
        public bool Running = false;                    // Is it running?
        private Encoding charEncoder = Encoding.UTF8;   // To encode string
        private Socket serverSocket;                    // Our server socket

        private static ManualResetEvent allDone = new ManualResetEvent(false);
        public event SocketConnectEventHandler ClientConnected;
        public event SocketConnectEventHandler ClientDisconnected;
        public event SocketSendMsgEventHandler ClientSendMsg;
        private Tools tool = new Tools();

        public HttpResponseCore()
        {
            
        }


        public bool Start(IPAddress ipAddress, int port, int maxNOfCon)
        {
            if (Running) { return false; }
            IPEndPoint localEndPoint = new IPEndPoint(ipAddress, port);
            // Create a TCP/IP socket.
            serverSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
            // Bind the socket to the local endpoint and listen for incoming connections.
            try
            {
                serverSocket.Bind(localEndPoint);
                Running = true;
                allDone.Reset();
                serverSocket.Listen(maxNOfCon);
                serverSocket.BeginAccept(new AsyncCallback(acceptCallback), serverSocket);
                bool isRequest = allDone.WaitOne(new TimeSpan(0, 0, 0, 30));  // Blocks for 12 hours

                if (!isRequest)
                {
                    allDone.Set();
                    // Do some work here every 12 hours
                }
            }
            catch (System.ObjectDisposedException ex)
            {
                ServerMainLogic.GetServer().WriteLog(ex.Message);
            }
            catch (System.NotSupportedException ex)
            {
                ServerMainLogic.GetServer().WriteLog(ex.Message);
            }
            catch (System.InvalidOperationException ex)
            {
                ServerMainLogic.GetServer().WriteLog(ex.Message);
            }
            catch (System.ArgumentOutOfRangeException ex)
            {
                ServerMainLogic.GetServer().WriteLog(ex.Message);
            }
            catch (System.Net.Sockets.SocketException ex)
            {
                ServerMainLogic.GetServer().WriteLog(ex.Message);
            }
            catch (Exception ex)
            {
                ServerMainLogic.GetServer().WriteLog(ex.Message);
            }
            return true;
        }

        /// <summary>
        /// 停止Socket 监听
        /// </summary>
        public void Stop()
        {
            if (Running)
            {
                Running = false;
                try { serverSocket.Close(); }
                catch { }
                serverSocket = null;
            }
        }
        
        private void acceptCallback(IAsyncResult ar)
        {
            // Signal the main thread to continue.
            allDone.Set();

            // Get the socket that handles the client request.
            Socket listener = (Socket)ar.AsyncState;
            Socket handler = listener.EndAccept(ar);
            // Create the client.
            Client client = new Client(this);
            client.clientSocket = handler;
            try
            {
                if (ClientConnected != null)
                {
                    SocketConnectEventArgs se = new SocketConnectEventArgs();
                    se.Event = "连接成功 : time : " + tool.GetTimeStamp();
                    se.client = client;
                    se.httpContent = "";
                    ClientConnected(this, se);
                }
                serverSocket.BeginAccept(new AsyncCallback(acceptCallback), serverSocket);
                while (true)
                {
                    readCallback(client);
                    //handler.Receive(client.bBuffer, 0, StateObject.BufferSize, 0, new AsyncCallback(readCallback), client);
                }
               // ary.AsyncWaitHandle.WaitOne(new TimeSpan(0, 30, 0), true);
              //  ary.AsyncWaitHandle.Close(); 
            }
            catch (Exception ex)
            {
                disconnected(client, "error beginReceive exception" + ex.Message);
            }
        }

        private void readCallback(Client ar)
        {
            String content = String.Empty;
            // Retrieve the client and the handler socket
            // from the asynchronous client.
            Client client = (Client)ar;
            Socket handler = client.clientSocket;
            if (handler.Connected == false)
            {
                return;
            }
            try
            {
                // Read data from the client socket. 
               // int bytesRead = handler.EndReceive(ar);
                int bytesRead = client.clientSocket.Receive(client.bBuffer, client.bBuffer.Length, 0);
                if (bytesRead > 0)
                {
                    // There  might be more data, so store the data received so far.
                    client.sbContent.Append(Encoding.ASCII.GetString(
                        client.bBuffer, 0, bytesRead));
                    // Check for end-of-file tag. If it is not there, read more data.
                    content = client.sbContent.ToString();
                    if (ClientSendMsg != null)
                    {
                        SocketConnectEventArgs se = new SocketConnectEventArgs();
                        se.Event = "发送请求";
                        se.client = client;
                        se.httpContent = content;
                        ClientSendMsg(this, se);
                    }
                    handleTheRequest(content, client);
                }
                else
                {
                    disconnected(client, "information ReadCallBack bytesRead <= 0 disconnect\r\n");
                }
            }
            catch (System.Net.Sockets.SocketException ex)
            {
                disconnected(client, "error  ReadCallBack exception dissconnect\r\n" + ex.Message);
            }
            catch (System.InvalidOperationException ex)
            {
                disconnected(client, "error  ReadCallBack exception dissconnect\r\n" + ex.Message);
            }
            catch (System.ArgumentNullException ex)
            {
                disconnected(client, "error  ReadCallBack exception dissconnect\r\n" + ex.Message);
            }
            catch (System.ArgumentException ex)
            {
                disconnected(client, "error  ReadCallBack exception dissconnect\r\n" + ex.Message);
            }
            catch (Exception ex)
            {
                disconnected(client, "error  ReadCallBack exception dissconnect\r\n" + ex.Message);
            }
        }

        private void send(Client handler, byte[] byteData)
        {
            // Begin sending the data to the remote device.
            handler.clientSocket.BeginSend(byteData, 0, byteData.Length, 0,
                new AsyncCallback(sendCallback), handler);
        }

        private void endSendCallBack(IAsyncResult ar)
        {
 
        }

        private void sendCallback(IAsyncResult ar)
        {
            // Retrieve the socket from the client.
            Client handler = (Client)ar.AsyncState;
            try
            {
                // Complete sending the data to the remote device.
                int bytesSent = handler.clientSocket.EndSend(ar);

                disconnected(handler, "AsyncSendCallBack send OK disconnect");
            }
            catch (System.Net.Sockets.SocketException ex)
            {
                disconnected(handler, "error AsyncSendCallBack send exception disconnect \n" + ex.Message);
            }
            catch (System.ObjectDisposedException ex)
            {
                disconnected(handler, "error AsyncSendCallBack send exception disconnect \n" + ex.Message);
            }
            catch (Exception ex)
            {
                disconnected(handler, "error AsyncSendCallBack send exception disconnect \n" + ex.Message);
            }
        }

        private void sendFileCallback(IAsyncResult ar)
        {
            // Retrieve the socket from the state object.
            Client handler = (Client)ar.AsyncState;
            try
            {
                handler.clientSocket.Shutdown(SocketShutdown.Both);
                disconnected(handler, "AsyncSendCallBack send OK disconnect");
            }
            catch (System.Net.Sockets.SocketException ex)
            {
                disconnected(handler, "error AsyncSendCallBack send exception disconnect \n" + ex.Message);
            }
            catch (System.ObjectDisposedException ex)
            {
                disconnected(handler, "error AsyncSendCallBack send exception disconnect \n" + ex.Message);
            }
            catch (Exception ex)
            {
                disconnected(handler, "error AsyncSendCallBack send exception disconnect \n" + ex.Message);
            }
        }
        
        /// <summary>
        /// 处理请求
        /// </summary>
        /// <param name="content"></param>
        /// <param name="client"></param>
        private void handleTheRequest(string content,Client client)
        {
            string httpMethod = content.Substring(0, content.IndexOf(" "));

            int start = content.IndexOf(httpMethod) + httpMethod.Length + 1;
            int length = content.LastIndexOf("HTTP") - start - 1;
            string requestedUrl = content.Substring(start, length);
            string Referer = tool.GetRefererFromHttpHeader(content);
            string requestedContent;
            string paramter = "";
            if (httpMethod.Equals("GET") || httpMethod.Equals("POST"))
            {
                requestedContent = requestedUrl.Split('?')[0];
                if (requestedUrl.Contains("?"))
                {
                    paramter = requestedUrl.Split('?')[1];
                }
            }
            else 
            {
                notImplemented(client);
                return;
            }

            requestedContent = requestedContent.Replace("/", @"\").Replace("\\", "");
            // TODO: 根据不同的htm 做不同的处理
            if (client.KeyHtm.ContainsKey(requestedContent))
            {
                client.KeyHtm[requestedContent](paramter, Referer);
            }
            else
            {
                notImplemented(client);
            }
        }

        /// <summary>
        ///  发送EXE文件
        /// </summary>
        /// <param name="client"></param>
        /// <param name="FileName">文件名</param>
        public void SendExeFileToClient(Client client, string fileName)
        {
            byte[] bHeader = charEncoder.GetBytes(
                                   "HTTP/1.1 200 OK\r\n"
                                 + "Server: LongChengTieJi Web Server\r\n"
                                 + "Connection: close\r\n"
                                 + "Content-Disposition: attachment; filename=" + fileName + " \r\n"
                                 + "Content-Type: " + HttpExtension.EXE + " \r\n\r\n");

            client.clientSocket.BeginSendFile(CreateFile.FileFoder + CreateFile.SourceFile, bHeader, null, TransmitFileOptions.Disconnect | TransmitFileOptions.UseKernelApc, new AsyncCallback(sendFileCallback), client);
        }

        private void notImplemented(Client client)
        {    
            SendResponse(client,
                "<html><head><meta http-equiv=\"Content-Type\" content=\"" + HttpExtension.HTML + "; charset=utf-8\"></head><body><h2>LongChengTieJi Web Server</h2><div>501 - Method Not Implemented</div></body></html>", 
                "501 Not Implemented", "text/html");
 
        }
 
        private void notFound(Client client)
        {   
            SendResponse(client,
                "<html><head><meta http-equiv=\"Content-Type\" content=\"" + HttpExtension.HTML + "; charset=utf-8\"></head><body><h2>LongChengTieJi Web Server</h2><div>404 - Not Found</div></body></html>", 
                "404 Not Found", "text/html");
        }
 
        public void SendOkResponse(Client client, byte[] bContent, string contentType)
        {
            sendResponse(client, bContent, "200 OK", contentType);
        }

        // For strings
        public void SendResponse(Client client, string strContent, string responseCode,
                                  string contentType)
        {
            byte[] bContent = charEncoder.GetBytes(strContent);
            sendResponse(client, bContent, responseCode, contentType);
        }

        /// <summary>
        /// 响应请求
        /// </summary>
        /// <param name="client"></param>
        /// <param name="bContent">发送的内容</param>
        /// <param name="responseCode"></param>
        /// <param name="contentType"></param>
        private void sendResponse(Client client, byte[] bContent, string responseCode,
                                  string contentType)
        {
            try
            {
                byte[] bHeader = charEncoder.GetBytes(
                                    "HTTP/1.1 " + responseCode + "\r\n"
                                  + "Server: LongChengTieJi Web Server\r\n"
                                  + "Content-Length: " + bContent.Length.ToString() + "\r\n"
                                  + "Connection: close\r\n"
                                  + "Content-Type: " + contentType + "\r\n\r\n");
                client.clientSocket.Send(bHeader);
                client.clientSocket.Send(bContent);
                //send(client, bContent);
            }
            catch (System.ArgumentNullException ex)
            {
                disconnected(client, "sendResponse exception " + ex.Message);
            }
            catch (System.Net.Sockets.SocketException ex)
            {
                disconnected(client, "sendResponse exception " + ex.Message);
            }
            catch (System.ObjectDisposedException ex)
            {
                disconnected(client, "sendResponse exception " + ex.Message);
            }
            catch (Exception ex)
            {
                disconnected(client, "sendResponse exception " + ex.Message);
            }
        }

        /// <summary>
        /// 网页重定向
        /// </summary>
        /// <param name="client"></param>
        /// <param name="url"></param>
        public void Redirect(Client client ,string url)
        {
            if (client.clientSocket.Connected == true)
            {
                string htmlPage = "<HTML><head> <meta HTTP-EQUIV=\"REFRESH\" content=\"0; url="+url+"\"></head></HTML>";
                sendResponse(client, charEncoder.GetBytes(htmlPage), "200 OK", HttpExtension.HTML);
            }
        }

        /// <summary>
        /// 关闭Socket
        /// </summary>
        /// <param name="client"></param>
        /// <param name="msg">output message</param>
        private void disconnected(Client client,string msg)
        {
            if (ClientDisconnected != null)
            {
                SocketConnectEventArgs se = new SocketConnectEventArgs();
                se.httpContent = msg;
                se.client = client;
                se.Event = "断开连接 ： time:" + tool.GetTimeStamp();
                if (client.clientSocket.Connected == true)
                { 
                    ClientDisconnected(this, se);
                }
            }
            ServerMainLogic.GetServer().WriteLog(msg);
            client.clientSocket.Shutdown(SocketShutdown.Both);
            client.clientSocket.Close();
            GC.Collect();
        }
    }
}
